写在最前面

如果说要评选 Android 对于新人最友好的第三方库,我觉得 Butterknife 如果称第二的话,没人敢称第一。其友好在于两点:第一是,面向的功能简单,初衷就是为了替代 findViewByid 方法,使得更关于逻辑上的开发;第二是,使用简单,一个注解就将 View 与资源文件元素绑定了起来。我们知道,这种越接近“底层”的方法,就越难模拟,所以 Butterknife 的技术使用可以一点不简单,那么这个系列我们就来看看,对于 Butterknife 它的技术栈都用了哪些技术。

虽然现在大部分原生开发已经转换到 Kotlin 上了,但是对 ButterKnife 的学习依然是有意义的。

注解

注解是 Butterknife 中,最表象的一个技术。本篇中,我们来看看注解是怎样的一个逻辑。

注解,大家都了解,Android 开发中最常见的注解就是 @Overrider 当你复写父类的方法时,会在方法前面加上这个注解。用来表明这是一个覆盖了父类的方法,那么这里我们来看看 @Overrider 里面是怎样的逻辑。

元注解

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.SOURCE)
public @interface Override {
}

我们看到,Overrider 上面依然被两个注解来修饰,这种修饰注解的注解被称为元注解。上面两个注解 Target Retention 是两个最常见元注解,也是我们在定义一个注解时会设定的注解。

Target

Target 元注解主要是说明,这个注解“用于修饰的对象是什么类型的”。这里类型,是 enum 类型 ElementType 的元素。

public enum ElementType {
    //“类型” 类型 如 class interface enum
    TYPE,
    //“变量” 类型
    FIELD,
    //“方法” 类型
    METHOD,
    //“参数” 类型
    PARAMETER,
    //“构造函数” 类型
    CONSTRUCTOR,
    //“局部变量” 类型
    LOCAL_VARIABLE,
    //“注解” 类型
    ANNOTATION_TYPE,
    //“报名” 类型
    PACKAGE,
}

例如 Override 是 @Target(ElementType.METHOD) 这个就是说明 Override 用于修饰方法,不能修饰其他类型。有如 BindView是 @Target(ElementType.FIELD) 因此 BindView 用于修饰类内的变量。当然 Target 参数可以是个数据支持多 target。默认所有都支持。

Retention

Retention 元注解主要是说明,“注解的可见性”。Retention 的可见性分为三类:

public enum RetentionPolicy {
    //仅在源码中生效
    SOURCE,
    //记录到 class 文件中
    CLASS,
    //运行时有效
    RUNTIME
}

其中,CLASS 是默认设置。如果有过逆向 App 的经验话,逆向后的代码依然会有 Override 修饰方法,这就是因为 Override “可见性”就是 CLASS 的。RUNTIME 是为了在运行时,来获得注解,进而通过反射来做一些事情。例如 BindView 的“可见性”就是 RUNTIME 所以,Butterknife 可以在运行时干一些事情。

注解参数

对于大部分运行时注解,都会需要参数,因此需要设置参数,注解中设置参数以如下方式:

@Retention(RUNTIME) @Target(FIELD)
public @interface BindView {
  /** View ID to which the field will be bound. */
  @IdRes int value();
}

这样,我们在调用的时候,传入资源 id 就可以了:

@BindView(value = R.id.btn) Button mButton;

对于只有一个参数的情况,可以省去 key ,直接 @BindView(R.id.btn) 如果有两个以上的参数,就需要全部说明。

对于参数,我们也可以提供默认值

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TestAnno {
    public int id() default -1;
    public String name() default "";

}

这样使用时可以使用默认值,这里我们就不给出例子了。

自定义注解

除了系统 SDK 提供,第三方库提供,我们还可以自定义注解。例如:

@IntDef({ iOS, Android, unknown })

@Retention(SOURCE)
@Target({FIELD,PARAMETER})
public @interface Platform {
  int iOS = 0;
  int Android = 1;
  int unknown = 2;
}

有些涉及到多端的功能,可能会需要分清楚运行平台,这时候使用注解来对相应的平台进行修饰是个很好的选择。这里我们定义的了一个 Platform 的注解,用这个注解来修饰变量和参数:

@Platform int mPlatform = Platform.Android;
...
public void onPlatform(@Platform int platform){
    //todo
}

这样表现的 platform 的含义会非常清楚。

反射

之前我们学习过 Java 虚拟机的相关内容,其中重要的一点,就是类在运行之前会加载到虚拟中,无论是 String Java语言自带的类,还是自己定义的类。

只有加载之后,虚拟机才获取这类的相关信息,类型判断也就是这个时间之后才会确定的。

我们可以利用这个特性,来动态的获取某些平时无法直接修改熟悉等。

简单的来说,反射提供了一个思路,就是从类方面下手,来获取或者执行相应的操作,而不是从对象下手。可以在运行时获得程序或程序集中每一个类型的成员和成员的信息。

反射的核心是 JVM 在运行时才动态加载类或调用方法/访问属性

基本使用

获取 Class 对象

Class 对象,差不多是反射机制的入口了,常用三种方法来获取一个 Class 对象:

public static Class<?> forName(String className) //通过名字来获取 Class,主要类名要包含完整路径。
Class<?> clazz = Activity.class; // 直接调用 .class 方法来获取 Class
Class<?> clazz = str.getClass(); // 调用实例的 getClass 方法

三种方法都能获得一个 Class 对象。

获取 实例 对象

Class<?> c = String.class;
Object str = c.newInstance();  // 使用Class对象的newInstance()方法来创建Class对象对应类的实例

//获取String所对应的Class对象
Class<?> c = String.class;
//获取String类带一个String参数的构造器
Constructor constructor = c.getConstructor(String.class);
//根据构造器创建实例
Object obj = constructor.newInstance("23333");
System.out.println(obj);

获取 Field 对象

Field 对象,获取一个类中的成员变量

mClass.getFields(); //包括本类声明的和从父类继承的
mClass.getDeclaredFields(); // 获取所有本类声明的变量(不问访问权限)

获取 Method 对象

Method 对象,获取一个类中的方法

mClass.getMethods();    //包括自己声明和从父类继承的
mClass.getDeclaredMethods();//获取所有本类的的方法(不问访问权限)

method.getReturnType(); //返回类型
method.getParameters(); //获取并输出方法的所有参数

对私有方法/属性进行调用/修改

核心思路就是调用 setAccessible(true),来使其获取权限,进而可以写操作 private 类型对象和方法。

小结

本篇主要就是从定义注解入手,分析了注解,元注解等基本概念,最后给出了一种在 Android 中,常使用注解的一种方式,至于如何在编译/运行时处理注解,我们放到后面再说,下篇见。

results matching ""

    No results matching ""